# -*- coding: utf-8 -*-
# Gestión del diseño gráfico de clases, relaciones y texto.

## vipera, un diseñador de aplicaciones para Python
##     Copyright (C) 2011 Ángel Luis García García
##
##    This program is free software: you can redistribute it and/or modify
##    it under the terms of the GNU General Public License as published by
##    the Free Software Foundation, either version 3 of the License, or
##    (at your option) any later version.
##
##    This program is distributed in the hope that it will be useful,
##    but WITHOUT ANY WARRANTY; without even the implied warranty of
##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##    GNU General Public License for more details.
##
##    You should have received a copy of the GNU General Public License
##    along with this program.  If not, see <http://www.gnu.org/licenses/>.

import wx
import wx.lib.ogl as ogl

from clases_modelo import modelo_clases, modelo_relaciones
from clases_modelo import modelo_relaciones_dependencia, modelo_texto_ogl
from clases_relaciones_vista import panel_crear_relacion, panel_eliminar_relacion
from clases_orden_vipera import dialogo_orden
from configuracion_vipera import proy_por_defecto
from i18n_vipera import t

class panel_crear_rel(panel_crear_relacion):
    '''Panel de creación de relación entre clases'''
    def __init__(self, parent, lista_clases = None, idioma = None):
        '''Constructor de clase'''        
        panel_crear_relacion.__init__(self, parent, idioma = None)
        self.padre = parent
        self.Centre()
        # Incluimos la lista de clases.
        if lista_clases is not None:
            self.m_comboBox_padre.AppendItems(lista_clases)
            self.m_comboBox_hijo.AppendItems(lista_clases)

    def OnAceptar(self, event):
        '''Event handler de creación de relación'''
        # Obtenemos nombre de clases padre e hijo.
        npadre = self.m_comboBox_padre.GetStringSelection()
        nhijo = self.m_comboBox_hijo.GetStringSelection()
        if len(npadre) == 0 or len(nhijo) == 0: return
        # Padre e hijo no pueden ser el mismo.
        if npadre == nhijo:
            msj = t(u'Una clase no puede heredar de si misma', self.l)
            wx.MessageBox(msj, t(u"Atención", self.l), wx.OK)
            return
        # Si existe la relación, no se puede volver a crear.
        if self.padre.padre.relacion.existe_relacion(npadre, nhijo):
            msj = t(u'¡La relación ya existe!', self.l)
            wx.MessageBox(msj, t(u"Atención", self.l), wx.OK)
            return
        # Obtenemos los dividedshape.
        padre = self.padre.padre.modelo.get_divided_shape(npadre)
        hijo = self.padre.padre.modelo.get_divided_shape(nhijo)
        # Línea.
        line = ogl.LineShape()
        line.SetCanvas(self.padre.padre.panel.canvas)
        # line = ogl.LineShape()
        # line.SetCanvas(self)
        line.SetPen(wx.BLACK_PEN)
        line.SetBrush(wx.BLACK_BRUSH)
        line.AddArrow(ogl.ARROW_ARROW)
        line.MakeLineControlPoints(2)
        # Marcamos el sentido de la flecha.
        hijo.AddLine(line, padre)
        # Lo añadimos al canvas.
        self.padre.padre.panel.canvas.AddShape(line)
        line.Show(True)
        # Refrescamos el padre.
        self.padre.padre.panel.Refresh()
        # Incluimos la relación.
        self.padre.padre.relacion.crear_relacion(npadre, nhijo)
        # Limpiamos memoria.
        self.padre.Destroy()

    def OnCancelar(self, event):
        '''Event handler para salir'''
        # Limpiamos memoria.
        self.padre.Destroy()

class panel_eliminar_rel(panel_eliminar_relacion):
    '''Panel de eliminación de relación entre clases'''
    def __init__(self, parent, lista_relaciones = None, idioma = None):
        '''Constructor de clase'''        
        panel_eliminar_relacion.__init__(self, parent, idioma = idioma)
        # Cambiamos idioma.
        
        self.padre = parent
        self.Centre()
        # Incluimos la lista de clases.
        if lista_relaciones is not None:
            self.m_comboBox_relacion.AppendItems(lista_relaciones)

    # ##############################################
    # Hacemos override de los manejadores de eventos
    # diseñados en wxFormBuilder.
    # ##############################################

    def OnAceptar(self, event):
        '''Event handler de eliminación de relación'''
        # Obtenemos relación.
        relacion = self.m_comboBox_relacion.GetStringSelection()
        if len(relacion) == 0: return
        # Obtenemos identificadores de padre e hijo.
        npadre, nhijo = self.padre.padre.relacion.get_padre_hijo(relacion)
        # Obtenemos los dividedshape.
        padre = self.padre.padre.modelo.get_divided_shape(npadre)
        hijo = self.padre.padre.modelo.get_divided_shape(nhijo)
        # Lista de flechas.
        lista_flechas = hijo.GetLines()
        # Recorremos lista de flechas del dividedshape.
        for i in lista_flechas:
            # ¿Flecha correcta?
            if padre == i.GetTo(): 
                i.Delete()
        # Refrescamos el padre.
        self.padre.padre.panel.Refresh()
        # Eliminamos la relación.
        self.padre.padre.relacion.eliminar_relacion(relacion)
        # Limpiamos memoria.
        self.padre.Destroy()

    def OnCancelar(self, event):
        '''Event handler para salir'''
        # Limpiamos memoria.
        self.padre.Destroy()

class frame_crear_relacion(wx.Frame):
    '''Frame para crear una relación entre dos clases'''
    def __init__(self, parent, lista_clases = None, idioma = None):
        '''Constructor de clase'''        
        wx.Frame.__init__(self, parent)
        self.panel = panel_crear_rel(self, lista_clases, idioma = None)
        self.padre = parent
        self.Centre()

class frame_eliminar_relacion(wx.Frame):
    '''Frame para eliminar una relación entre dos clases'''
    def __init__(self, parent, lista_relaciones = None, idioma = None):
        '''Constructor de clase'''        
        wx.Frame.__init__(self, parent)
        self.panel = panel_eliminar_rel(self, lista_relaciones, idioma = None)
        self.padre = parent
        self.Centre()

class frame_eliminar (wx.Frame):
    '''Frame para pedir eliminación de clase'''
    def __init__(self, parent, lista_clases, idioma = None ):
        '''Constructor de clase'''        
        wx.Frame.__init__(self, parent, title = t(u"Seleccione clase a eliminar", idioma),
                          size = wx.Size( 500,142 ), 
                          style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
        # Idioma.
        self.l = idioma
        self.padre = parent
        bSizer1 = wx.BoxSizer( wx.VERTICAL )
        m_choice1Choices = lista_clases
        self.m_choice1 = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choice1Choices, 0 )
        self.m_choice1.SetSelection( 0 )
        bSizer1.Add( self.m_choice1, 0, wx.ALL|wx.EXPAND, 10 )
        bSizer2 = wx.BoxSizer( wx.HORIZONTAL )
        self.m_button_aceptar = wx.Button( self, wx.ID_ANY, t(u"Aceptar", self.l), wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer2.Add( self.m_button_aceptar, 0, wx.ALL|wx.EXPAND, 10 )
        self.m_button_cancelar = wx.Button( self, wx.ID_ANY, t(u"Cancelar", self.l), wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_button_cancelar.SetDefault() 
        bSizer2.Add( self.m_button_cancelar, 0, wx.ALL|wx.EXPAND, 10 )
        bSizer1.Add( bSizer2, 0, wx.ALL|wx.EXPAND, 5 )
        self.SetSizer( bSizer1 )
        self.Layout()
        self.Centre( wx.BOTH )
        # Binding.
        self.m_button_aceptar.Bind( wx.EVT_BUTTON, self.OnAceptar )
        self.m_button_cancelar.Bind( wx.EVT_BUTTON, self.OnCancelar )

    def OnAceptar(self,event):
        '''Event Handler que devuelve el elemento seleccionado'''
        nombre_clase_a_eliminar = self.m_choice1.GetStringSelection()
        if len(nombre_clase_a_eliminar.strip()) == 0: return
        # Elemento a eliminar.
        clase_a_eliminar = self.padre.modelo.get_divided_shape(nombre_clase_a_eliminar)
        # [02-06-2011] La clase puede tener relaciones. Hay que eliminar
        # tanto las relaciones de entrada como las de salida hacia la  clase a eliminar.
        # Lista de clases.
        lista_clases = self.padre.modelo.clases()
        # Recorremos todos los dividedshape.
        for i in lista_clases:
            # Lista de flechas.
            lista_flechas = i[1].GetLines()
            # Nombre de clase.
            nom_clase = i[0].nombre
            # Dividedshape actual
            ds_actual = i[1] 
            # Recorremos lista de flechas del dividedshape actual.
            for j in lista_flechas:
                if nombre_clase_a_eliminar == nom_clase: continue
                # ¿Flecha de entrada ó de salida)
                if clase_a_eliminar == j.GetTo():
                    self.padre.relacion.eliminar_relacion2(nombre_clase_a_eliminar, nom_clase)
                    # Eliminamos la flecha del canvas.
                    j.Delete()
                if clase_a_eliminar == j.GetFrom():
                    self.padre.relacion.eliminar_relacion2(nom_clase, nombre_clase_a_eliminar)
                    # Eliminamos la flecha del canvas.
                    j.Delete()
        # Eliminamos el elemento del canvas.
        clase_a_eliminar.Delete()
        # Refrescamos.
        self.padre.panel.Refresh()
        # Eliminamos clase del modelo.
        self.padre.modelo.eliminar_clase(nombre_clase_a_eliminar)
        # Limpiamos memoria.
        self.Destroy()

    def OnCancelar(self,event):
        '''Event Handler para salir'''
        self.padre.elemento_a_eliminar = None
        self.Destroy()

class MyEvtHandler(ogl.ShapeEvtHandler):
    '''Manejador de eventos de elementos del canvas'''
    def __init__(self):        
        '''Constructor de clase'''        
        ogl.ShapeEvtHandler.__init__(self)

    def UpdateStatusBar(self, shape):
        '''Actualización de barra de estado'''
        x, y = shape.GetX(), shape.GetY()
        width, height = shape.GetBoundingBoxMax()

    def OnRightClick(self, *dontcare):
        '''Botón derecho ratón'''
        pass

    def OnLeftClick(self, x, y, keys=0, attachment=0):
        '''Botón izquierdo ratón'''
        shape = self.GetShape()
        canvas = shape.GetCanvas()
        dc = wx.ClientDC(canvas)
        canvas.PrepareDC(dc)
        if shape.Selected():
            shape.Select(False, dc)
            #canvas.Redraw(dc)
            canvas.Refresh(False)
        else:
            redraw = False
            shapeList = canvas.GetDiagram().GetShapeList()
            toUnselect = []
            for s in shapeList:
                if s.Selected():
                    toUnselect.append(s)
            shape.Select(True, dc)
            if toUnselect:
                for s in toUnselect:
                    s.Select(False, dc)
                canvas.Refresh(False)
        self.UpdateStatusBar(shape)

    #def cod_sel_clase(self):
        #'''Método para incluir código al seleccionar un DividedShape'''
        #pass

    def OnEndDragLeft(self, x, y, keys=0, attachment=0):
        '''OnEndDragLeft'''
        shape = self.GetShape()
        ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment)
        if not shape.Selected():
            self.OnLeftClick(x, y, keys, attachment)
        self.UpdateStatusBar(shape)

    def OnSizingEndDragLeft(self, pt, x, y, keys, attch):
        '''OnSizingEndDragLeft'''
        ogl.ShapeEvtHandler.OnSizingEndDragLeft(self, pt, x, y, keys, attch)
        self.UpdateStatusBar(self.GetShape())

    def OnMovePost(self, dc, x, y, oldX, oldY, display):
        '''OnMovePost'''
        shape = self.GetShape()
        ogl.ShapeEvtHandler.OnMovePost(self, dc, x, y, oldX, oldY, display)
        self.UpdateStatusBar(shape)

class MyEvtHandler_p(MyEvtHandler):
    '''Manejador de eventos para elementos del canvas
    con funcionalidades de actualización de paneles'''
    def __init__(self, panel_conf = None, panel_ogl = None):
        '''Constructor de clase'''        
        MyEvtHandler.__init__(self)
        self.panel_conf = panel_conf
        self.panel_ogl = panel_ogl
        
    #def OnRightClick(slef, x, y, keys = 0, attachment=0):
    #    wx.MessageBox("CLICK","",wx.YES)

    def OnLeftClick(self, x, y, keys=0, attachment=0):
        '''Botón izquierdo ratón'''
        # Llamamos al método padre, para evitar el override.
        MyEvtHandler.OnLeftClick(self, x, y, keys=0,attachment=0)
        # Claes en edición.
        clase_en_edicion = self.panel_conf.panel_def_clases.m_textCtrl_nc.GetValue()
        if len(clase_en_edicion.strip()) != 0: 
            # self.panel_conf.panel_met_clases.salvar_clase()
            # Definición de clase.
            self.panel_conf.panel_def_clases.salvar_def_clase()
            # Parámetros de instanciación de clase.
            self.panel_conf.panel_param_clases.salvar_param_inst_clase()
            # Atributos de clase.
            self.panel_conf.panel_atrib_clases.salvar_atrib_clase()
            # Métodos de clase.
            self.panel_conf.panel_met_clases.salvar_info_metodo()
            self.panel_conf.panel_met_clases.salvar_param_metodo()
            self.panel_conf.panel_met_clases.salvar_editor_metodo()
            # Propiedades de clase.
            self.panel_conf.panel_prop_clases.salvar_prop_clase()
            # Información de clase.
            self.panel_conf.panel_info_clases.salvar_info_clase()
            # Constantes de clase.
            self.panel_conf.panel_cte_clases.salvar_cte_clase()
            # Constructor de clase.
            self.panel_conf.panel_const_clases.salvar_constructor_clase()
            # Destructor de clase.
            self.panel_conf.panel_dest_clases.salvar_destructor_clase()
        # Llamamos al código a ejecutar.
        self.cod_sel_clase()
                
    def cod_sel_clase(self):
        '''Método para traspasar datos del DividedShape a las características'''
        # Obtenemos clases.
        clases = self.panel_ogl.panel_ogl.modelo.clases()
        # Clase actual.
        ds = self.GetShape()
        # Identificamos la clase a editar en el modelo.
        for i in clases: 
            
            if i[1] == ds:
                
                # ###########################################
                # Incluir datos en el panel de configuración.
                # ###########################################
                
                # #################
                # Incluimos nombre.
                # #################
                
                self.panel_conf.panel_def_clases.m_textCtrl_nc.SetValue(i[0].nombre)

                # #########################################
                # Incluimos clases padre de las que hereda.
                # #########################################
                
                texto = ''
                for j in self.panel_ogl.panel_ogl.relacion.relaciones():
                    if j[2] == i[0].nombre: 
                        if len(texto) == 0: texto += j[1]
                        else: texto += '%s%s' % (', ',j[1])
                self.panel_conf.panel_def_clases.m_textCtrl_hereda.SetValue(texto)

                # #####################################################
                # Incluimos clases externas padre de las que hereda y
                # el código Python extra que se incluye en el __init__.
                # #####################################################
                
                if i[0].clases_externas is None: i[0].clases_externas = ''
                # if i[0].codigo_init is None: i[0].codigo_init = ''
                self.panel_conf.panel_def_clases.m_textCtrl_hereda_ext.SetValue(i[0].clases_externas)
                # self.panel_conf.m_nombre_clase.editor.panel.editor.SetText(i[0].codigo_init)
                
                # ##############################
                # Incluimos comentario de clase.
                # ##############################
                
                if i[0].comentario is None: i[0].comentario = ''
                self.panel_conf.panel_info_clases.m_textCtrl_coment.SetValue(i[0].comentario)

                # #############################################
                # Incluimos código constructor inicial y final.
                # #############################################
                
                # Recogemos datos.
                if len(i[0].codigo_constructor_ini) == 0:
                    cod_const_ini = ''
                    flag_ini = False
                else:
                    cod_const_ini = i[0].codigo_constructor_ini[0]
                    flag_ini = i[0].codigo_constructor_ini[1]
                    
                if len(i[0].codigo_constructor_fin) == 0:
                    cod_const_fin = ''
                    flag_fin = False
                else:
                    cod_const_fin = i[0].codigo_constructor_fin[0]
                    flag_fin = i[0].codigo_constructor_fin[1]
                
                # Pasamos datos.
                self.panel_conf.panel_const_clases.editor_ini.panel.editor.SetText(cod_const_ini)
                self.panel_conf.panel_const_clases.editor_fin.panel.editor.SetText(cod_const_fin)
                self.panel_conf.panel_const_clases.m_checkBox_ini.SetValue(flag_ini)
                self.panel_conf.panel_const_clases.m_checkBox_fin.SetValue(flag_fin)
                
                # ############################
                # Incluimos código destructor.
                # ############################

                # Recogemos datos.
                if len(i[0].codigo_destructor) == 0:
                    cod_dest = ''
                    flag = False
                else:
                    cod_dest = i[0].codigo_destructor[0]
                    flag = i[0].codigo_destructor[1]
                
                # Pasamos datos.
                self.panel_conf.panel_dest_clases.editor.panel.editor.SetText(cod_dest)
                self.panel_conf.panel_dest_clases.m_checkBox_ini.SetValue(flag)
                
                # ##############################
                # Incluimos constantes de clase.
                # ##############################

                # Limpiamos...
                self.panel_conf.panel_cte_clases.m_lista_cte.DeleteAllItems()
                
                # Recogemos datos.
                lista_cte = i[0].constantes
                
                # Pasamos datos.
                for cte in lista_cte: 
                    n_cte = self.panel_conf.panel_cte_clases.m_lista_cte.GetItemCount()
                    self.panel_conf.panel_cte_clases.m_lista_cte.InsertStringItem(n_cte, cte)
                
                # ######################################
                # Incluimos parámetros de instanciación.
                # ######################################
                
                # Limpiamos toda la estructura.
                for j in self.panel_conf.panel_param_clases.control_texto:
                    j[0].SetValue('')
                    j[1].SetValue('')
                # Incluimos datos.
                indice = 0
                for j in i[0].parametros: # self.panel_ogl.modelo.parametros:
                    k = self.panel_conf.panel_param_clases.control_texto[indice]
                    k[0].SetValue(j[0])
                    k[1].SetValue(j[1])
                    indice += 1

                # #############################
                # Incluimos atributos de clase.
                # #############################
                
                # Limpiamos toda la estructura.
                for j in self.panel_conf.panel_atrib_clases.control_texto:
                    j[0].SetValue('')
                    j[1].SetValue('')
                    j[2].Set3StateValue(wx.CHK_CHECKED) # SetSelection(0)
                # Incluimos datos.
                indice = 0
                for j in i[0].atributos: 
                    k = self.panel_conf.panel_atrib_clases.control_texto[indice]
                    k[0].SetValue(j[0])
                    k[1].SetValue(j[1])
                    k[2].Set3StateValue(j[2])
                    indice += 1                    

                # ###############################
                # Incluimos propiedades de clase.
                # ###############################
                
                # Limpiamos toda la estructura.
                for j in self.panel_conf.panel_prop_clases.control_texto:
                    j[0].SetValue('')
                    j[1].SetValue('')
                    j[2].SetValue('')
                # Incluimos datos.
                indice = 0
                for j in i[0].propiedades: 
                    k = self.panel_conf.panel_prop_clases.control_texto[indice]
                    k[0].SetValue(j[0])
                    k[1].SetValue(j[1])
                    k[2].SetValue(j[2])
                    indice += 1   
                    
                # ###########################
                # Incluimos métodos de clase.
                # ###########################
                
                # Primero limpiamos todo, por si nos dejamos algo anteriormente.
                self.panel_conf.panel_met_clases.m_panel_izq.m_info.SetValue('')
                self.panel_conf.panel_met_clases.m_panel_izq.metodo_oculto.Set3StateValue(wx.CHK_UNCHECKED) # .SetSelection(1)
                for j in self.panel_conf.panel_met_clases.m_panel_izq.control_texto:
                    j[0].SetValue('')
                    j[1].SetValue('')
                    j[2].SetSelection(0)
                    # j[3].SetSelection(0)
                self.panel_conf.panel_met_clases.m_panel_der.panel.editor.SetText('')
                # También limpiamos el combo con los nombres de los métodos de clase.
                self.panel_conf.panel_met_clases.m_metodos.SetItems(list())
                # Ahora recorremos estructura interna, creando los métodos.
                lista_metodos = list()
                for j in i[0].metodos:
                    lista_metodos.append(j[0])
                self.panel_conf.panel_met_clases.m_metodos.AppendItems(lista_metodos)
              
            
class DividedShape(ogl.DividedShape):
    '''Caja gráfica de entidad clase DividedShape'''
    def __init__(self, width, height, canvas, nombre_clase, propiedades,
                 atributos, metodos):
        '''Constructor de clase'''                
        ogl.DividedShape.__init__(self, width, height)
        # Enter.
        enter = '\n'
        # Tipo de letra.
        tipo_letra = wx.Font(8, wx.MODERN, wx.NORMAL, wx.NORMAL, False)
        # Clase.
        region1 = ogl.ShapeRegion()
        region1.SetText(nombre_clase.strip())
        region1.SetColour('Red')
        region1.SetProportions(0.0, 0.1)
        region1.SetFont(tipo_letra)
        region1.SetFormatMode(ogl.FORMAT_CENTRE_HORIZ)
        self.AddRegion(region1)
        # Propiedades y atributos.
        region2 = ogl.ShapeRegion()
        cadena = ''
        for i in propiedades: cadena += '@%s%s' % (i, enter)
        #cadena = cadena + '--------' + enter
        if atributos is not None:
            for i in atributos: cadena = cadena + i + enter
        region2.SetText(cadena)
        region2.SetProportions(0.0, 0.4)
        region2.SetFont(tipo_letra)
        region2.SetColour('Green')
        region2.SetFormatMode(ogl.FORMAT_CENTRE_HORIZ)
        self.AddRegion(region2)
        # Métodos.
        region3 = ogl.ShapeRegion()
        cadena = ''
        if metodos is not None:
            for i in metodos: cadena = cadena + i + enter
        region3.SetText(cadena)
        region3.SetProportions(0.0, 0.8)
        region3.SetFont(tipo_letra)
        region3.SetColour('Blue')
        region3.SetFormatMode(ogl.FORMAT_CENTRE_HORIZ)
        self.AddRegion(region3)
        # Pintamos las divisiones en el dividedshape.
        self.SetRegionSizes()
        self.ReformatRegions(canvas)

    def ReformatRegions(self, canvas=None):
        '''ReformatRegions'''
        rnum = 0
        if canvas is None:
            canvas = self.GetCanvas()
        dc = wx.ClientDC(canvas)  # used for measuring
        for region in self.GetRegions():
            text = region.GetText()
            self.FormatText(dc, text, rnum)
            rnum += 1

    def OnSizingEndDragLeft(self, pt, x, y, keys, attch):
        '''OnSizingEndDragLeft'''
        ogl.DividedShape.OnSizingEndDragLeft(self, pt, x, y, keys, attch)
        self.SetRegionSizes()
        self.ReformatRegions()
        self.GetCanvas().Refresh()

class panel_design(wx.Panel):
    '''Panel del canvas OGL'''
    def __init__(self, parent, clases = None):
        '''Constructor de clase'''
        # Creamos el frame contenedor.
        wx.Panel.__init__( self, parent)
        # El sizer que contendrá todo.
        sizer = wx.BoxSizer( wx.VERTICAL )
        # Creamos el canvas.
        self.canvas = ogl.ShapeCanvas( self )
        # Y lo metemos en el sizer, claro.
        sizer.Add( self.canvas, 1, wx.GROW )
        # Le damos un color de fondo al canvas.
        self.canvas.SetBackgroundColour("WHITE")
        # Definimos el ancho y alto máximo del shapecanvas, con barras
        # de desplazamiento.
        ancho_maximo = proy_por_defecto['ogl_ancho_maximo']
        alto_maximo = proy_por_defecto['ogl_alto_maximo']
        self.canvas.SetScrollbars(20, 20, ancho_maximo/20, alto_maximo/20)
        # Añadimos al sizer.
        self.SetSizer(sizer)

    def configurar_canvas(self, alto, ancho, color):
        '''Configuración del ogl'''
        self.canvas.SetBackgroundColour(color)
        self.canvas.SetScrollbars(20, 20, ancho/20, alto/20)
        
    def crear_diagrama(self):
        '''Creación de un diagrama nuevo'''
        self.diagram = ogl.Diagram()
        self.canvas.SetDiagram(self.diagram )
        self.diagram.SetCanvas( self.canvas )
    
class panel_design_class(wx.Panel):
    '''Panel de diseño gráfico de diagramas de clases'''
    def __init__(self, parent, manejador_por_defecto = MyEvtHandler, 
                 params = None, panel_principal = None, idioma = None):
        '''Constructor de clase'''        
        wx.Panel.__init__(self, parent)
        # Idioma.
        self.l = idioma
        # Referencia al panel principal.
        self.panel_principal = panel_principal
        # [20-06-2011] Manejador por defecto de los DividedShape.
        self.__manejador = manejador_por_defecto
        self.__params = params
        # Creamos el modelo de clases.
        self.modelo = modelo_clases()
        # [01-06-2011] Creamos el modelo de relaciones.
        self.relacion = modelo_relaciones()
        # [23-06-2011] Creamos el modelo de relaciones de dependencia.
        self.relacion_dep = modelo_relaciones_dependencia()
        # [21-10-2011] Creamos modelo de mensajes de texto.
        self.mensajes_texto = modelo_texto_ogl()
        # Panel de diseño de OGL.
        self.panel = panel_design(self)
        # Sizer principal.
        sizer = wx.BoxSizer(wx.VERTICAL)
        # E incluimos el panel dentro del sizer principal.
        sizer.Add(self.panel,1,wx.ALL|wx.EXPAND,1)        
        self.SetSizer(sizer)
        # Creamos nuevo diagrama.
        self.panel.crear_diagrama()
        # Binding.
        self.panel.canvas.Bind(wx.EVT_UPDATE_UI, self.OnUpdateCanvas)
        
    def OnUpdateCanvas(self, event):
        '''OnUpdateCanvas'''
        shapeList = self.panel.canvas.GetDiagram().GetShapeList()
        for s in shapeList:
            if s.Selected():
                self.panel_principal.m_panel_der_clases.Enable(True)
                return
        if self.panel_principal.m_panel_der_clases.IsEnabled():
            # Desactivamos panel.
            self.panel_principal.m_panel_der_clases.Enable(False)
            # Limpiamos definición de clase.
            self.panel_principal.panel_def_clases.limpiar_widget()
            # Limpiamos parámetros de instanciación.
            self.panel_principal.panel_param_clases.limpiar_widget()
            # Limpiamos atributos.
            self.panel_principal.panel_atrib_clases.limpiar_widget()
            # Limpiamos métodos.
            self.panel_principal.panel_met_clases.limpiar_widget()
            # Limpiamos lista de métodos.
            self.panel_principal.panel_met_clases.m_metodos.SetItems([])
            # Limpiamos propiedades de clase.
            self.panel_principal.panel_prop_clases.limpiar_widget()
            # Limpiamos información de clase.
            self.panel_principal.panel_info_clases.limpiar_widget()
            # Limpiamos constantes.
            self.panel_principal.panel_cte_clases.limpiar_widget()
            # Limpiamos constructor y destructor.
            self.panel_principal.panel_const_clases.limpiar_widget()
            self.panel_principal.panel_dest_clases.limpiar_widget()
        
    def limpiar_widget(self):
        '''Elimina todos los shapes del diagram OGL'''
        self.panel.diagram.DeleteAllShapes()
        self.panel.Refresh()        
        
    def OnEliminarTexto(self, event):
        '''Se elimina un texto del OGL'''
        # Detectamos el shape seleccionado.
        shapeList = self.panel.canvas.GetDiagram().GetShapeList()
        contador = 0
        for s in shapeList:
                if s.Selected():
                    shape = s
                    contador += 1
        # Evaluamos el resultado de la selección.
        if contador > 1:
            msj = t(u'No se puede eliminar con multiselección', self.l)
            wx.MessageBox(msj, t(u"Atención", self.l), wx.OK)
            return
        if contador == 0:
            msj = t(u'Primero se debe seleccionar el texto a eliminar', self.l)
            wx.MessageBox(msj, t(u"Atención", self.l), wx.OK)
            return
        if not isinstance(shape, ogl.TextShape):
            msj = t(u'El elemento seleccionado no es un texto', self.l)
            wx.MessageBox(msj, t(u"Atención", self.l), wx.OK)
            return
        # Preguntamos si se quiere eliminar...
        i = wx.MessageBox(t(u"¿Eliminar texto?", self.l), t(u"Atención", self.l), wx.YES_NO)
        if i != wx.YES: return
        # Cambiamos texto en el OGL.
        # shape.Delete()
        # Cambiamos el modelo.
        self.mensajes_texto.eliminar_texto(shape)
        self.Refresh()
        
    def OnModificarTexto(self, event):
        '''Se modifica un texto del OGL'''
        # Detectamos el shape seleccionado.
        shapeList = self.panel.canvas.GetDiagram().GetShapeList()
        contador = 0
        for s in shapeList:
                if s.Selected():
                    shape = s
                    contador += 1
        # Evaluamos el resultado de la selección.
        if contador > 1:
            msj = t(u'No se puede modificar con multiselección', self.l)
            wx.MessageBox(msj, t(u"Atención", self.l), wx.OK)
            return
        if contador == 0:
            msj = t(u'Primero se debe seleccionar el texto a modificar', self.l)
            wx.MessageBox(msj, t(u"Atención", self.l), wx.OK)
            return
        if not isinstance(shape, ogl.TextShape):
            msj = t(u'El elemento seleccionado no es un texto', self.l)
            wx.MessageBox(msj, t(u"Atención", self.l), wx.OK)
            return
        # Pedimos texto.
        texto = self.__pedir_datos(1)
        if texto is None: return
        if len(texto.strip()) == 0:
            wx.MessageBox(t(u"No puede incluir textos vacíos", self.l), t(u"Atención", self.l), wx.OK)
            return
        # Cambiamos texto en el OGL.
        shape.ClearText()
        # Líneas del texto.
        for linea in texto.split('\\n'):
            shape.AddText(linea)
        self.panel.Refresh()
        # Cambiamos el modelo.
        self.mensajes_texto.modificar_texto(texto, shape)

    def OnCrearTexto(self, event):
        '''Se crea un texto en el OGL'''
        # Pedimos texto.
        texto = self.__pedir_datos(1)
        if texto is None: return
        if len(texto.strip()) == 0:
            wx.MessageBox(t(u"No puede incluir textos vacíos", self.l), t(u"Atención", self.l), wx.OK)
            return
        # Creamos caja.
        shape = ogl.TextShape(120, 45)
        # Coordenadas.
        valor_x = 100.0
        valor_y = 100.0
        # Posición del texto.
        shape.SetX(valor_x)
        shape.SetY(valor_y)
        # Color y brocha...
        shape.SetPen(wx.GREEN_PEN)
        shape.SetBrush(wx.LIGHT_GREY_BRUSH)
        # Líneas del texto.
        for linea in texto.split('\\n'):
            shape.AddText(linea)
        #shape.AddText(texto)
        # Añadimos texto al canvas.
        self.panel.canvas.AddShape(shape)
        # Lo mostramos.
        self.panel.diagram.ShowAll(1)        
        self.Refresh() 
        # Manejadores de eventos para que funcione...
        evthandler = MyEvtHandler()
        evthandler.SetShape(shape)
        evthandler.SetPreviousHandler(shape.GetEventHandler())
        shape.SetEventHandler(evthandler)        
        # Faltaría incluirlo en alguna estructura interna...
        self.mensajes_texto.add_texto(texto, shape)
        
    def __pedir_datos(self, opcion = 0):
        '''Pedir datos mediante un wx.TextEntryDialog.'''
        if opcion == 0: cadena = t(u'Nombre de la nueva clase: ', self.l)
        if opcion == 1: cadena = t(u'Texto (use \\n para nueva línea): ', self.l)
        dialogo = wx.TextEntryDialog(self, cadena, t(u'Clase', self.l))
        dialogo.Show() 
        if dialogo.ShowModal() == wx.ID_OK:
            ret = dialogo.GetValue()
        else:
            # Botón cancelar. Nada que devolver.
            ret = None
        # Limpiamos memoria y nos vamos.
        dialogo.Destroy()
        return ret        

    def OnOrdenCreacion(self, event):
        '''Event Handler para reorganizar los órdenes de instanciación de clases'''
        clases = self.modelo.clases()
        lista_clases_orig = list()
        for i in clases: lista_clases_orig.append(i[0].nombre.strip())
        lista_clases_orig.reverse()
        a = dialogo_orden(self, lista_clases_orig, idioma = self.l)
        a.Show()
        if a.ShowModal() == wx.ID_OK:
            # Número de elementos.
            lista_clases = list()
            elem = a.m_listCtrl_orden
            nelem = elem.GetItemCount()
            for i in range(nelem):
                j = elem.GetItem(i).GetText()
                # Creando lista de clases con nueva ordenación.
                lista_clases.append(j)
            # Ordenamos clases.
            self.modelo.ordenar_clases_por_nombre(lista_clases)
        # Limpiamos memoria y nos vamos.
        a.Destroy()

    def OnEliminarRelacion(self, event):
        '''Event handler para eliminar una relación'''
        # Obtenemos lista de relaciones.
        rel = self.relacion.relaciones()
        # Si no hay relaciones, nos vamos.
        if len(rel) == 0: return
        # Recuperamos los nombres de las relaciones.
        nombres = []
        for i in rel: nombres.append(i[0])
        # Frame de eliminación de relaciones.
        j = frame_eliminar_relacion(self, nombres, idioma = self.l)
        j.Show()

    def OnCrearRelacion(self, event):
        '''Event handler para crear una relación'''
        # Obtenemos lista de nombres de clases.
        clases = self.modelo.clases()
        nombres = []
        for i in clases: nombres.append(i[0].nombre)
        # Para crear una relación tiene que haber más de una clase.
        if len(nombres) <= 1: return
        # Frame de creación de relaciones.
        j = frame_crear_relacion(self, nombres, self.l)
        j.Show()

    def __eliminar(self):
        '''Caja de eliminación'''
        # Obtenemos nombres de clases.
        clases = self.modelo.clases()
        nombres = []
        for i in clases: nombres.append(i[0].nombre)
        # Frame de selección de clase a eliminar.
        j = frame_eliminar(self, nombres, self.l)
        j.Show()

    def OnEliminarClase(self, event):
        '''Event Handler para eliminar clase'''
        # Llamamos al frame de eliminación.
        self.__eliminar()
        
    def OnCrearClase(self, event):
        '''Event Handler para crear clase'''
        # Nombre de la clase.
        i = self.__pedir_datos()
        if i is None or len(i.strip()) == 0: return
        # Si existe el nombre de la clase, nos vamos.
        if self.modelo.existe_clase(i): 
            msj = t(u'¡La clase ya existe!', self.l)
            wx.MessageBox(msj, t(u"Atención", self.l), wx.OK)
            return
        # Creamos caja.
        shape = DividedShape(140, 150, self, i,[],[],[])
        # Coordenadas.
        valor_x = 100.0
        valor_y = 100.0
        # Posición de la caja.
        shape.SetX( valor_x )
        shape.SetY( valor_y )
        # Añadimos la caja al canvas.
        self.panel.canvas.AddShape(shape)
        # Lo mostramos.
        self.panel.diagram.ShowAll( 1 )        
        # Manejadores de eventos.
        # [20-06-2011] Parametrización del manejador de eventos.
        if self.__params is None:
            evthandler = self.__manejador()
        else:
            evthandler = self.__manejador(self.__params[0], self.__params[1]) 
        evthandler.SetShape(shape)
        evthandler.SetPreviousHandler(shape.GetEventHandler())
        shape.SetEventHandler(evthandler)
        # Refrescamos el frame, para que salga todo.
        self.Refresh()       
        # Y creamos en el modelo el dividedshape.
        self.modelo.crear_clase(i,shape)

    def OnRenombrarClase(self, event):
        '''Manejador de eventos para renombrar una clase'''
        clases = self.modelo.clases()
        for i in clases:
            divided_shape = i[1]
            if divided_shape.Selected():
                # Nuevo nombre de la clase.
                j = self.__pedir_datos()
                if j is None or len(j.strip()) == 0: return
                # Si existe el nombre de la clase, nos vamos.
                if self.modelo.existe_clase(j): 
                    msj = t(u'¡La clase ya existe!', self.l)
                    wx.MessageBox(msj, t(u"Atención", self.l), wx.OK)
                    return
                # Modificamos el nombre de la clase.
                i[0].nombre = j.strip()
                # También modificamos el nombre en el OGL.
                self.ogl_nombre_clase(divided_shape, j.strip())
                # Y además modificamos en el panel de características.
                self.panel_principal.panel_def_clases.m_textCtrl_nc.SetValue(j.strip())
                # Y salimos.
                break      
            
    def ogl_nombre_clase(self, divided_shape, nombre_clase):
        '''Método para cambiar el nombre de la clase en un DividedShape'''
        # Obtenemos las regiones del DividedShape.
        lista = divided_shape.GetRegions()
        # Nombre de clase a cambiar.
        old_name = lista[0].GetText()
        # Cambiamos el nombre en el sistema de relaciones.
        self.relacion.renombrar_relacion(old_name, nombre_clase)
        # Nos quedamos con la región del nombre de clase y le cambiamos el texto.
        lista[0].SetText(nombre_clase)
        # Pintamos las divisiones en el dividedshape.
        divided_shape.SetRegionSizes()
        divided_shape.ReformatRegions(divided_shape.GetCanvas())   
        # Y refrescamos el canvas.
        divided_shape.GetCanvas().Refresh()

    def ogl_nombre_metodos(self, divided_shape, nombre_metodos):
        '''Método para cambiar los métodos de la clase en un DividedShape'''
        # Obtenemos las regiones del DividedShape.
        lista = divided_shape.GetRegions()
        # Formamos métodos.
        met = ''
        enter = '\n'
        for i in nombre_metodos:
            met += '%s%s' % (i,enter)
        # Nos quedamos con la región de los métodos.
        lista[2].SetText(met)
        # Pintamos las divisiones en el dividedshape.
        divided_shape.SetRegionSizes()
        divided_shape.ReformatRegions(divided_shape.GetCanvas())   
        # Y refrescamos el canvas.
        divided_shape.GetCanvas().Refresh()

    def ogl_nombre_atributos(self, divided_shape, nombre_atributos, nombre_propiedades):
        '''Método para cambiar los atributos y @propiedades de la clase en un DividedShape'''
        # Obtenemos las regiones del DividedShape.
        lista = divided_shape.GetRegions()
        # Formamos atributos.
        atrib = ''
        enter = '\n'
        for i in nombre_atributos:
            atrib += '%s%s' % (i[0],enter)
        # Formamos propiedades con el prefijo @.
        for i in nombre_propiedades:
            atrib += '@%s%s' % (i[0],enter)
        # Nos quedamos con la región de los atributos.
        lista[1].SetText(atrib)
        # Pintamos las divisiones en el dividedshape.
        divided_shape.SetRegionSizes()
        divided_shape.ReformatRegions(divided_shape.GetCanvas())   
        # Y refrescamos el canvas.
        divided_shape.GetCanvas().Refresh()
        
    def dibujar_relacion_dependencia(self, padre, hijo):
        '''Método para dibujar relaciones de dependencia entre clases'''
        # Línea.
        line = ogl.LineShape()
        line.SetCanvas(self.panel.canvas)
        line.SetPen(wx.RED_PEN) # wx.LIGHT_GREY_PEN)
        line.SetBrush(wx.RED_BRUSH) # LIGHT_GREY_BRUSH)
        line.AddArrow(ogl.ATTACHMENT_MODE_BRANCHING)
        line.MakeLineControlPoints(2)
        # Marcamos el sentido de la flecha.
        # hijo.AddLine(line, padre)
        padre.AddLine(line, hijo)
        # Lo añadimos al canvas.
        self.panel.canvas.AddShape(line)
        line.Show(True)
        # Refrescamos el padre.
        self.panel.Refresh()

    def eliminar_dibujo_relacion_dependencia(self, padre, hijo):
        '''Método para eliminar relaciones de dependencia entre clases'''
        # Lista de flechas.
        lista_flechas = hijo.GetLines()
        # Recorremos lista de flechas del dividedshape.
        for i in lista_flechas:
            # ¿Flecha correcta?
            if padre == i.GetTo(): 
                i.Delete()
        # Refrescamos el padre.
        self.panel.Refresh()
    
    def salvar_datos(self):
        '''Método que recorre las clases y las devuelve en un diccionario'''
        # Modelo de datos de clases.
        modelo = self.modelo.clases_sin_divided_shape()
        # Modelo de relaciones entre clases.
        relacion = self.relacion.relaciones()
        # Obtenemos todas las clases del módulo.
        clases_p = self.modelo.clases()
        # Obtenemos cajas de texto gráfico.
        mensajes_texto = self.mensajes_texto.textos()
        # Recorremos las cajas, obteniendo sus posiciones.
        posiciones_texto = list()
        for i in mensajes_texto:
            # Posición del dividedshape.
            pos_x = i[1].GetX()
            pos_y = i[1].GetY()
            height = i[1].GetHeight()
            width = i[1].GetWidth()
            # Datos de texto.
            texto = unicode(i[0]).strip()
            posiciones_texto.append((texto,pos_x, pos_y, width, height))
        # Recorremos todas las clases, obteniendo 
        # la posición de cada dividedshape.
        posicion_cajas = list()
        for i in clases_p:
            # Posición del dividedshape.
            pos_x = i[1].GetX()
            pos_y = i[1].GetY()
            # Datos de clase.
            nombre = unicode(i[0].nombre).strip()
            posicion_cajas.append((nombre,pos_x, pos_y))
        # Creamos diccionario de clases (y métodos) y cajas de texto.
        clases = {'modelo' : modelo,
                  'relacion' : relacion,
                  'posicion_cajas' : posicion_cajas,
                  'posiciones_texto' : posiciones_texto}
        # Devolvemos diccionario.
        return clases

    def cargar_datos(self, dict_clases):
        '''Cargamos clases en modelo y widget ogl'''
        if len(dict_clases) == 0:
            self.modelo = modelo_clases()
            self.relacion = modelo_relaciones()
            self.relacion_dep = modelo_relaciones_dependencia()
            self.mensajes_texto = modelo_texto_ogl()
            return
        # Obtenemos estrucuras internas.
        modelo = dict_clases['modelo']
        posicion_cajas = dict_clases['posicion_cajas']
        relacion = dict_clases['relacion']
        # [21-10-2011] Caja de texto.
        mensajes_texto = dict_clases['posiciones_texto']
        # Referenciamos el canvas.
        canvas = self.panel.canvas 
        # Recorremos el modelo de clases para pintar los dividedshape.
        for k in modelo:
            clase = k[0]
            nombre = clase.nombre
            atributos = list()
            for j in clase.atributos: atributos.append(j[0])
            propiedades = list()
            for j in clase.propiedades: propiedades.append(j[0])
            metodos = list()
            for j in clase.metodos: metodos.append(j[0])
            # Creamos el DividedShape.
            shape = DividedShape(140, 150, canvas, nombre, propiedades, atributos, metodos)
            # Incluimos la referencia del dividedshape en la estructura.
            k[1] = shape
            # Coordenadas.
            for coord in posicion_cajas:
                if nombre == coord[0]:
                    valor_x = coord[1]
                    valor_y = coord[2]
                    break
            # Posición de la caja.
            shape.SetX(valor_x)
            shape.SetY(valor_y)
            # Añadimos la caja al canvas.
            canvas.AddShape(shape)
            # Lo mostramos.
            self.panel.diagram.ShowAll(1)
            # Manejador de eventos del dividedshape.
            evthandler = MyEvtHandler_p(self.panel_principal, self.panel_principal)
            evthandler.SetShape(shape)
            evthandler.SetPreviousHandler(shape.GetEventHandler())
            shape.SetEventHandler(evthandler)
            # Refrescamos el frame, para que salga todo.
            self.Refresh # page.panel_c.p_design.Refresh()  
        # Incluimos las estructuras.
        self.modelo.set_clases(modelo)
        self.relacion.set_relaciones(relacion)
        # Creamos relaciones de herencia en el OGL.
        for k in relacion:
            npadre = k[1]
            nhijo = k[2] 
            padre = self.modelo.get_divided_shape(npadre)
            hijo = self.modelo.get_divided_shape(nhijo)
            # Línea.
            line = ogl.LineShape()
            line.SetCanvas(canvas)
            line.SetPen(wx.BLACK_PEN)
            line.SetBrush(wx.BLACK_BRUSH)
            line.AddArrow(ogl.ARROW_ARROW)
            line.MakeLineControlPoints(2)
            # Marcamos el sentido de la flecha.
            hijo.AddLine(line, padre)
            # Lo añadimos al canvas.
            canvas.AddShape(line)
            line.Show(True)
            # Refrescamos.
            self.Refresh()
        # Obtenemos todos los nombres de las clases.
        lista_clases = list()
        for k in modelo: lista_clases.append(k[0].nombre)
        # Recorremos cada clase para dibujar posibles relaciones de dependencia.
        for k in modelo:
            nombre_clase = k[0].nombre
            atributos = k[0].atributos
            clase_hijo = k[1] # DividedShape actual.
            # Recorremos todos los atributos.
            for h in atributos:
                valor_predet = h[1]
                # Nombre de clase que está en el valor predeterminado del atributo.
                nombre_clase_padre = valor_predet.split("(")[0].strip()
                # Una clase no se puede autoreferenciar.
                if nombre_clase == nombre_clase_padre: continue
                # Referencia del dividedshape de la clase padre.
                clase_padre = self.modelo.get_divided_shape(nombre_clase_padre)
                # Si no existe, seguimos.
                if clase_padre is None: continue
                # Creamos la relación de dependencia.
                self.relacion_dep.crear_relacion(clase_padre, clase_hijo)
                # Pintamos la relación en el OGL.
                line = ogl.LineShape()
                line.SetCanvas(canvas)
                line.SetPen(wx.RED_PEN) # wx.LIGHT_GREY_PEN)
                line.SetBrush(wx.RED_BRUSH) #wx.LIGHT_GREY_BRUSH)
                line.AddArrow(ogl.ATTACHMENT_MODE_BRANCHING)
                line.MakeLineControlPoints(2)
                # Marcamos el sentido de la flecha.
                # clase_hijo.AddLine(line, clase_padre)
                clase_padre.AddLine(line, clase_hijo)
                # Lo añadimos al canvas.
                canvas.AddShape(line)
                line.Show(True)
                # Refrescamos.
                self.Refresh()
        # [21-10-2011] Recorremos cajas de texto.  
        self.mensajes_texto.eliminar_todo()     # ELIMINAMOS TODO LO DEL BUFFER!!!
        for k in mensajes_texto:
            # Rescatamos datos.
            texto = k[0]
            coor_x = k[1]
            coor_y = k[2]
            width = k[3]
            height = k[4]
            # Creamos una caja de texto.
            shape = ogl.TextShape(width, height)
            # Color y brocha...
            shape.SetPen(wx.GREEN_PEN)
            shape.SetBrush(wx.LIGHT_GREY_BRUSH)
            # Líneas del texto.
            for linea in texto.split('\\n'):
                shape.AddText(linea)
            shape.SetX(coor_x)
            shape.SetY(coor_y)
            # Añadimos la caja al canvas.
            canvas.AddShape(shape)
            # Lo mostramos.
            self.panel.diagram.ShowAll(1)
            # Manejador de eventos.
            evthandler = MyEvtHandler()
            evthandler.SetShape(shape)
            evthandler.SetPreviousHandler(shape.GetEventHandler())
            shape.SetEventHandler(evthandler)
            # Refrescamos el frame, para que salga todo.
            self.Refresh()  
            # Incluimos las estructuras.
            self.mensajes_texto.add_texto(texto, shape)
        